#include <stdio.h> 
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

char *src_path = "a.bin";
char *dest_path = "dest.bin";

char *default_path = "./dest.bin";

int            src_len  = 0;
unsigned int   src_file_ver  = 0;
unsigned short src_check_sum = 0x5555;
unsigned char  file_head[256] = {0};
char           file_commit_message[120] = {0};
#define debug_info_print printf

int ret = 0;
unsigned char buff[1024] = {0};


void print_opt_info(void)
{

        printf("please check your input:        \n");
        printf("    -i   input_file.bin         \n");
        printf("    -c   des.config             \n");
        printf("    [-o] output_file.bin        \n");
}


int main(int argc, char *argv[])
{
	int ch;

        if(argc < 5){
                print_opt_info();
                return EXIT_FAILURE;
        }
       
       /*
	* -i 源文件
	* -v 版本
	* -m 描述信息
	* -o 目标文件，可选参数，默认为./dest.bin
	*/	
	while((ch = getopt(argc, argv, "i:v:m:o:")) != -1)
        {
                switch(ch)
                {
                        case 'i':
                                src_path     = optarg;
                                break;
                        case 'v':
				src_file_ver = atoi(optarg);
				printf("src_file_ver=%x\n", src_file_ver);
                                break;
                        case 'm':
				if(strlen(optarg) > sizeof(file_commit_message)){
					memcpy(file_commit_message, optarg, sizeof(file_commit_message));
				}else{
					memcpy(file_commit_message, optarg, strlen(optarg));
				}
				printf("commit message info:\n");
				printf("%s\n", optarg);
                                break;
                        case 'o':
                                dest_path    = optarg;
                                break;
                        default:
                                break;
                }
        }

        if(src_path == NULL){
                print_opt_info();
                return EXIT_FAILURE;
        }
        if(dest_path == NULL){
                dest_path = default_path;
        }

	/* 
	 * 打开源文件
	 */
	FILE *src_fp = fopen(src_path, "rb");
       	if (!src_fp) {
		perror("fopen src_fp");
		return EXIT_FAILURE;
	}
	/*
	 * 打开目标文件
	 */
	FILE *dest_fp = fopen(dest_path, "w+");
	if (!dest_fp) {
		perror("fopen dest_fp");
		return EXIT_FAILURE;
	}

	/*
	 * 将源文件的文件指针偏移到文件尾，获取文件长度
	 */
	fseek(src_fp, 0, SEEK_END);
	src_len = ftell(src_fp);
	debug_info_print("src file len = %d\n", src_len);

	/*
	 * 将源文件的文件指针偏移到文件头
	 */
	fseek(src_fp, 0, SEEK_SET);

	/*
	 * 将目标文件的文件指针偏移到第256字节
	 */
	fseek(dest_fp, 256, SEEK_SET);
	src_check_sum = 0x5555;


	/*
	 * 循环读取源文件计算校验同时将源文件拷贝到目标文件中
	 */
	int read_count = 0, write_count = 0;
        while((ret = fread(buff, sizeof(*buff), sizeof(buff)/sizeof(*buff), src_fp)) > 0)
	{
		for(int i = 0; i < ret; i++){
			src_check_sum += buff[i];
		}
		read_count += ret;

		if(ret > 0){
			write_count += fwrite(buff, sizeof(*buff), ret, dest_fp);
		}
	}

	/*
	 * 比较文件长度，读取出的长度，写入的长度，判断是否操作成功
	 */
	if((read_count != src_len) || (read_count != write_count)){
		return EXIT_FAILURE;
	}	
	debug_info_print("src_check_sum = 0x%x\n", src_check_sum);

	/*
	 * 文件长度
	 */
	file_head[0] = src_len&0xFF;
	file_head[1] = (src_len>>8)&0xFF;
	file_head[2] = (src_len>>16)&0xFF;
	file_head[3] = (src_len>>24)&0xFF;

	/*
	 * 文件版本
	 */
	file_head[4] = (src_file_ver>>24)&0xFF;
	file_head[5] = (src_file_ver>>16)&0xFF;
	file_head[6] = (src_file_ver>>8)&0xFF;
	file_head[7] = src_file_ver&0xFF;


	for (int i = 8; i < 128; i++){
		file_head[i] = file_commit_message[i-8];
	}

	/*
	 * 文件校验和
	 */
	file_head[130] = src_check_sum&0xFF;
	file_head[131] = (src_check_sum>>8)&0xFF;

	unsigned short head_sum = 0x5555;	
	for(int i = 0; i < 254; i++){
		head_sum += file_head[i];
	}
	file_head[254] = head_sum&0xFF;
	file_head[255] = (head_sum>>8)&0xFF;
	
	/*
	 * 将目标文件的文件指针偏移至文件头，写入file_head
	 */	
	fseek(dest_fp, 0, SEEK_SET);
	if((fwrite(file_head, sizeof(*file_head), sizeof(file_head)/sizeof(*file_head), dest_fp)) != sizeof(file_head)){
		return EXIT_FAILURE;
	}

	fclose(src_fp);
	fclose(dest_fp);

	return EXIT_SUCCESS;
} 
